using Moq;
using Xunit;

namespace Moq.Tests
{
	public class AutoMockContainerFixture
	{
		[Fact]
		public void CreatesLooseMocksIfFactoryIsLoose()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			var component = factory.Create<TestComponent>();

			component.RunAll();
		}

		[Fact]
		public void CreatesClassUsingActivator()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			var service = new ServiceA();
			var component = factory.Create<TestComponent>(r => new TestComponent(service, r.Resolve<IServiceB>()));

			Assert.NotNull(component);
			Assert.Same(service, component.ServiceA);
			Assert.NotNull(component.ServiceB);
		}

		[Fact]
		public void CanRegisterImplementationAndResolveIt()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			factory.Register<IServiceA, ServiceA>();

			var service = factory.Resolve<IServiceA>();

			Assert.NotNull(service);
			Assert.False(service is IMocked<IServiceA>);
		}


		[Fact]
		public void CanRegisterImplementationWithDelegateAndResolveIt()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			factory.Register<IServiceA>(r => new ServiceA(5) { Value = "foo" });

			var service = factory.Resolve<IServiceA>() as ServiceA;

			Assert.NotNull(service);
			Assert.False(service is IMocked<IServiceA>);
			Assert.Equal(5, service.Count);
			Assert.Equal("foo", service.Value);
		}

		[Fact]
		public void CanRegisterImplementationWithDelegateResolveAndResolveIt()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			factory.Register<IServiceA>(r => new ServiceA(r.Resolve<IServiceB>()));

			var service = factory.Resolve<IServiceA>() as ServiceA;

			Assert.NotNull(service);
			Assert.False(service is IMocked<IServiceA>);
			Assert.NotNull(service.ServiceB);
			Assert.True(service.ServiceB is IMocked<IServiceB>);
		}

		[Fact]
		public void ResolveUnregisteredImplementationReturnsMock()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));

			var service = factory.Resolve<IServiceA>();

			Assert.NotNull(service);
			Assert.True(service is IMocked<IServiceA>);
		}

		[Fact]
		public void DefaultConstructorWorksWithAllTests()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Loose));
			var a = false;
			var b = false;
			factory.GetMock<IServiceA>().Setup(x => x.RunA()).Callback(() => a = true);
			factory.GetMock<IServiceB>().Setup(x => x.RunB()).Callback(() => b = true);

			var component = factory.Create<TestComponent>();
			component.RunAll();

			Assert.True(a);
			Assert.True(b);
		}

		[Fact]
		public void ThrowsIfStrictMockWithoutSetupation()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Strict));
			factory.GetMock<IServiceB>().Setup(x => x.RunB());

			var component = factory.Create<TestComponent>();

			Assert.Throws<MockException>(() => component.RunAll());
		}

		[Fact]
		public void StrictWorksWithAllSetupationsMet()
		{
			var factory = new AutoMockContainer(new MockFactory(MockBehavior.Strict));
			factory.GetMock<IServiceA>().Setup(x => x.RunA());
			factory.GetMock<IServiceB>().Setup(x => x.RunB());

			var component = factory.Create<TestComponent>();
			component.RunAll();
		}

		public interface IServiceA
		{
			void RunA();
		}

		public interface IServiceB
		{
			void RunB();
		}

		public class ServiceA : IServiceA
		{
			public ServiceA()
			{
			}

			public ServiceA(int count)
			{
				this.Count = count;
			}

			public ServiceA(IServiceB b)
			{
				this.ServiceB = b;
			}

			public IServiceB ServiceB { get; private set; }
			public int Count { get; private set; }

			public string Value { get; set; }

			public void RunA() { }
		}

		public class TestComponent
		{
			public TestComponent(IServiceA serviceA, IServiceB serviceB)
			{
				this.ServiceA = serviceA;
				this.ServiceB = serviceB;
			}

			public IServiceA ServiceA { get; private set; }
			public IServiceB ServiceB { get; private set; }

			public void RunAll()
			{
				this.ServiceA.RunA();
				this.ServiceB.RunB();
			}
		}
	}
}